iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
佛心分享-IT 人技術創業

30 天打造東亞財報全文 API:PDF 解析、亂碼修復、章節切分到 MCP 上線實錄系列 第 2

一人公司的架構選型:為什麼「採集留牆內、分發上 Cloudflare」

  • 分享至 

  • xImage
  •  

第一個版本的架構只有一台機器:一支 FastAPI、一個 SQLite 檔、一個放 markdown 的目錄。能跑,功能也對,壓力測試打下去幾百 QPS 就開始抖。

但它真正的問題不是「幾百 QPS 不夠」。是這個架構把三件性質完全不同的工作綁在同一台機器上:向來源抓資料(要特定地理位置的 IP)、把 PDF 解析成 markdown(CPU 密集)、對外提供 API(要全球低延遲)。這三件事的最佳位置不一樣,綁在一起,就註定有一邊要犧牲。

所以架構選型的第一步不是「選哪個框架」,而是「先承認這三件事不該住在一起」。

第一版為什麼撐不住

我把壓力測試時看到的問題列出來,一共四個,每一個都是「換一台更大的機器」解不掉的:

一、資料只在一台機器上。 這台機器在牆內,使用者在牆外。每一個請求都要跨過半個地球,這不是加 CPU 能解決的延遲。

二、每個請求都同步寫一次用量表。 SQLite 的寫入是單寫鎖,請求一多,全部擠在那一把鎖上排隊。這一條最致命:我把「記錄用量」和「回應請求」串成了同一條路徑。

三、每個請求都直接讀檔案。 沒有快取層,同一份熱門財報被反覆從磁碟撈。

四、速率限制的計數器放在單一行程的記憶體裡。 這種計數器沒辦法水平擴展——開第二台機器,兩邊的計數就對不上了。

結論很明確:這是一台「幾百 QPS 到頂」的原型,不是能對外的服務。

拆成兩層,按地理分工

拆法不是「前端/後端」,是「離線批次/線上服務」:

做什麼 放在哪 為什麼
離線採集與解析 抓公告、下載 PDF、解析成 markdown 牆內的輕量伺服器 來源要牆內 IP;解析是 CPU 密集,不需要對外
線上分發 提供 API、回傳 markdown Cloudflare 邊緣 靜態唯讀內容,天生適合 CDN

採集層不需要網域、也不需要 ICP 備案——因為它不對外提供任何服務,只是一台會自己跑排程的機器。這一點在選型時很重要:它讓採集層可以用最便宜的方案,隨便一台輕量伺服器(月租大概人民幣 50~100 元)就夠。

分發層的遷移路徑也很直接:

  • api.py → Cloudflare Workers
  • content/ 目錄 → R2
  • SQLite 的三張表 → D1(D1 本身就是 SQLite,表結構可以原樣搬)
  • 記憶體裡的速率限制計數器 → Durable Objects

注意最後一項:把計數器從行程記憶體搬到邊緣的共享儲存,這才是「能水平擴展」的關鍵。原本那個計數器綁在單一行程上,換到任何一台機器都會失效。

為什麼採集層不一起上雲?

既然 Cloudflare 免費又全球,為什麼採集不也搬上去?

因為採集要靠近的是來源,不是使用者。我的四個市場裡,中國的揭露平台會看來源 IP,從牆外的機器打過去就是不穩;這種情況下「離使用者近」一點用都沒有,「離來源近」才是唯一重要的事。反過來說,如果某個來源本來就在牆外(例如美國 SEC 這類公開來源),那我就用牆外的機器去拉,物理上根本不必翻牆。

採集不翻牆,是分流。 牆內的來源用牆內的機器,牆外的來源用牆外的機器。在牆內的伺服器上裝翻牆工具這件事,我從一開始就沒打算做——那除了技術上不穩,本身也是會出事的做法。

這個決定的代價

誠實講,兩層分離不是免費的:

第一,多了同步這一段。 本地寫入的 SQLite 和雲端的 D1 是兩份資料,中間靠一支同步程式(push.py)推。這代表「寫入」和「可查詢」之間有時間差。這個時間差在財報這種低頻資料上完全可接受(一天推一次都行),但它會在一個地方咬我——刪除。這個雷我留到 Day 5 講。

第二,多了一台機器的維運。 採集機要自己顧排程、自己看有沒有跑完。雲端那層反而是我最不用擔心的部分。

第三,我把 API 入口綁在單一平台上。 這是免費額度的代價——便宜,但不是沒有鎖定。

明天

這套架構之所以能成立,很大一部分是因為分發層「幾乎不用錢」。明天(Day 3)我把免費額度一項一項算出來:Workers 每天 10 萬個請求、D1 5GB、R2 10GB,這三個數字到底能撐多少?以及為什麼規模真的上來時,第一個要付的錢是每月 $5。


上一篇
財報的「數字」早就有人做了,但「本文」沒有
下一篇
免費檔能撐多少?把雲端成本算給你看
系列文
30 天打造東亞財報全文 API:PDF 解析、亂碼修復、章節切分到 MCP 上線實錄5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言